概述
Google在 Android 6.0 开始引入了权限申请机制,将所有权限分成了正常权限和危险权限。应用的相关功能每次在使用危险权限时需要动态的申请并得到用户的授权才能使用。动态权限申请的使用主要涉及到以下几种方法:
方法 | 作用 |
---|---|
checkSelfPermission(@NonNull String permission) | 检查权限 |
requestPermissions(@NonNull String[] permissions, int requestCode) | 申请权限 |
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) | 处理结果回调 |
shouldShowRequestPermissionRationale(@NonNull String permission) | 是否需要显示UI界面提示用户为什么需要这个权限 |
关于他们的用法可以参看另一篇文章《Android 6.0动态权限申请》,本文主要是通过分析源码深入理解权限管理工作的机制与原理。
权限申请
动态权限是通过Activity的requestPermissions方法来申请的,此方法会调用PackageManager的buildRequestPermissionsIntent创建动态权限申请的Intent,然后通过startActivityForResult方法广播出去等待返回结果。
frameworks/base/core/java/android/app/Activity.java
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
if (requestCode < 0) {
throw new IllegalArgumentException("requestCode should be >= 0");
}
if (mHasCurrentPermissionsRequest) {
Log.w(TAG, "Can request only one set of permissions at a time");
// Dispatch the callback with empty arrays which means a cancellation.
onRequestPermissionsResult(requestCode, new String[0], new int[0]);
return;
}
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
mHasCurrentPermissionsRequest = true;
}
frameworks/base/core/java/android/content/pm/PackageManager.java
public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
if (ArrayUtils.isEmpty(permissions)) {
throw new IllegalArgumentException("permission cannot be null or empty");
}
Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);
intent.setPackage(getPermissionControllerPackageName());
return intent;
}
权限申请广播发送出去后会启动应用PackageInstaller的GrantPermissionsActivity界面用于处理权限的动态申请。
packages/apps/PackageInstaller/AndroidManifest.xml
<activity android:name=".permission.ui.GrantPermissionsActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:excludeFromRecents="true"
android:theme="@style/GrantPermissions"
android:visibleToInstantApps="true">
<intent-filter android:priority="1">
<action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
GrantPermissionsActivity也就是我们常见的权限申请界面,用户可以根据提示选择是否授权给应用相应的权限。用户操作后的结果会通过回调GrantPermissionsActivity的onPermissionGrantResult方法返回。在onPermissionGrantResult方法中会根据返回结果去决定是走授予权限还是撤销权限流程,然后会更新授权结果,最后返回结果并结束自己。
@Override
public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) {
GroupState groupState = mRequestGrantPermissionGroups.get(name);
if (groupState.mGroup != null) {
if (granted) {
// 授予权限
groupState.mGroup.grantRuntimePermissions(doNotAskAgain,
groupState.affectedPermissions);
groupState.mState = GroupState.STATE_ALLOWED;
} else {
// 撤销权限
groupState.mGroup.revokeRuntimePermissions(doNotAskAgain,
groupState.affectedPermissions);
groupState.mState = GroupState.STATE_DENIED;
int numRequestedPermissions = mRequestedPermissions.length;
for (int i = 0; i < numRequestedPermissions; i++) {
String permission = mRequestedPermissions[i];
if (groupState.mGroup.hasPermission(permission)) {
EventLogger.logPermissionDenied(this, permission,
mAppPermissions.getPackageInfo().packageName);
}
}
}
// 更新授权结果
updateGrantResults(groupState.mGroup);
}
if (!showNextPermissionGroupGrantRequest()) {
// 返回授权结果并结束自己
setResultAndFinish();
}
}
接下来继续跟踪AppPermissionGroup.grantRuntimePermissions方法分析授权流程。AppPermissionGroup.grantRuntimePermissions方法中会判断targetSdkVersion是否大于LOLLIPOP_MR1(22),如果大于则做动态权限申请处理。
packages/apps/PackageInstaller/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java
public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
final int uid = mPackageInfo.applicationInfo.uid;
// We toggle permissions only to apps that support runtime
// permissions, otherwise we toggle the app op corresponding
// to the permission if the permission is granted to the app.
for (Permission permission : mPermissions.values()) {
...
if (mAppSupportsRuntimePermissions) {
// LOLLIPOP_MR1之后版本,支持动态权限申请
// Do not touch permissions fixed by the system.
if (permission.isSystemFixed()) {
return false;
}
// Ensure the permission app op enabled before the permission grant.
if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
permission.setAppOpAllowed(true);
mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
}
// Grant the permission if needed.
if (!permission.isGranted()) {
permission.setGranted(true);
mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
permission.getName(), mUserHandle);
}
// Update the permission flags.
if (!fixedByTheUser) {
// Now the apps can ask for the permission as the user
// no longer has it fixed in a denied state.
if (permission.isUserFixed() || permission.isUserSet()) {
permission.setUserFixed(false);
permission.setUserSet(false);
mPackageManager.updatePermissionFlags(permission.getName(),
mPackageInfo.packageName,
PackageManager.FLAG_PERMISSION_USER_FIXED
| PackageManager.FLAG_PERMISSION_USER_SET,
0, mUserHandle);
}
}
} else {
// LOLLIPOP_MR1之前版本,不支持动态权限申请
// Legacy apps cannot have a not granted permission but just in case.
....
}
}
return true;
}
在动态权限处理过程中终于看到了我们熟悉的身影PackageMananger,熟悉Android源码的同学都知道XXXManager只是一个辅助类,其真正提供服务的都是XXXManagerService,所以直接跳转PackageManagerService中的grantRuntimePermission方法。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@Override
public void grantRuntimePermission(String packageName, String permName, final int userId) {
mPermissionManager.grantRuntimePermission(permName, packageName, false /*overridePolicy*/,
getCallingUid(), userId, mPermissionCallback);
}
在PackageManagerService.grantRuntimePermission方法中,我们发现了Google在Android 9.0版本新添加了一个PermissionManagerInternal类用来管理动态权限申请,PermissionManagerService类为其提供相应的服务。在PermissionManagerService中的grantRuntimePermission方法中,首先会检查申请权限的应用是否存在,然后检查申请动态权限的应用是否具有授权权限,最后经过一系列处理后回调PermissionCallback的onPermissionGranted方法通知授予权限。
private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,
int callingUid, final int userId, PermissionCallback callback) {
// 检查用户是否存在
if (!mUserManagerInt.exists(userId)) {
Log.e(TAG, "No such user:" + userId);
return;
}
// 检查PackageInstaller是否有动态权限授权权限
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
"grantRuntimePermission");
...
// 回调PermissionCallback的onPermissionGranted方法通知授予权限
if (callback != null) {
callback.onPermissionGranted(uid, userId);
}
...
}
回调的是PackageManagerService中的PermissionCallback,在其实现的onPermissionGranted方法中会去通知观察者权限发生变化,并调用PackageManager的Settings记录动态权限授权状态。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@Override
public void onPermissionGranted(int uid, int userId) {
mOnPermissionChangeListeners.onPermissionsChanged(uid);
// Not critical; if this is lost, the application has to request again.
synchronized (mPackages) {
mSettings.writeRuntimePermissionsForUserLPr(userId, false);
}
}
从writeRuntimePermissionsForUserLPr方法继续跟踪,最后会回到RuntimePermissionsPersistence.writePermissionsSync方法来完成最后的记录工作。writePermissionsSync方法的代码很长,但是逻辑很清晰,就是先查询与应用相关的所有权限状态,然后创建xml文件把这些信息记录进去。
frameworks/base/services/core/java/com/android/server/pm/Settings.java
private void writePermissionsSync(int userId) {
// 动态权限文件(runtime-permissions.xml)
AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId),
"package-perms-" + userId);
ArrayMap<String, List<PermissionState>> permissionsForPackage = new ArrayMap<>();
ArrayMap<String, List<PermissionState>> permissionsForSharedUser = new ArrayMap<>();
synchronized (mPersistenceLock) {
mWriteScheduled.delete(userId);
// 获得Package权限状态
final int packageCount = mPackages.size();
for (int i = 0; i < packageCount; i++) {
String packageName = mPackages.keyAt(i);
PackageSetting packageSetting = mPackages.valueAt(i);
if (packageSetting.sharedUser == null) {
PermissionsState permissionsState = packageSetting.getPermissionsState();
List<PermissionState> permissionsStates = permissionsState
.getRuntimePermissionStates(userId);
if (!permissionsStates.isEmpty()) {
permissionsForPackage.put(packageName, permissionsStates);
}
}
}
// 获得SharedUser权限状态
final int sharedUserCount = mSharedUsers.size();
for (int i = 0; i < sharedUserCount; i++) {
String sharedUserName = mSharedUsers.keyAt(i);
SharedUserSetting sharedUser = mSharedUsers.valueAt(i);
PermissionsState permissionsState = sharedUser.getPermissionsState();
List<PermissionState> permissionsStates = permissionsState
.getRuntimePermissionStates(userId);
if (!permissionsStates.isEmpty()) {
permissionsForSharedUser.put(sharedUserName, permissionsStates);
}
}
}
FileOutputStream out = null;
try {
out = destination.startWrite();
// 创建xml文件用于记录权限状态
XmlSerializer serializer = Xml.newSerializer();
serializer.setOutput(out, StandardCharsets.UTF_8.name());
serializer.setFeature(
"http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.startDocument(null, true);
serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);
String fingerprint = mFingerprints.get(userId);
if (fingerprint != null) {
serializer.attribute(null, ATTR_FINGERPRINT, fingerprint);
}
// 写入Package权限状态
final int packageCount = permissionsForPackage.size();
for (int i = 0; i < packageCount; i++) {
String packageName = permissionsForPackage.keyAt(i);
List<PermissionState> permissionStates = permissionsForPackage.valueAt(i);
serializer.startTag(null, TAG_PACKAGE);
serializer.attribute(null, ATTR_NAME, packageName);
writePermissions(serializer, permissionStates);
serializer.endTag(null, TAG_PACKAGE);
}
// 写入SharedUser权限状态
final int sharedUserCount = permissionsForSharedUser.size();
for (int i = 0; i < sharedUserCount; i++) {
String packageName = permissionsForSharedUser.keyAt(i);
List<PermissionState> permissionStates = permissionsForSharedUser.valueAt(i);
serializer.startTag(null, TAG_SHARED_USER);
serializer.attribute(null, ATTR_NAME, packageName);
writePermissions(serializer, permissionStates);
serializer.endTag(null, TAG_SHARED_USER);
}
serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
// 写入结束
serializer.endDocument();
destination.finishWrite(out);
if (Build.FINGERPRINT.equals(fingerprint)) {
mDefaultPermissionsGranted.put(userId, true);
}
// Any error while writing is fatal.
} catch (Throwable t) {
Slog.wtf(PackageManagerService.TAG,
"Failed to write settings, restoring backup", t);
destination.failWrite(out);
} finally {
IoUtils.closeQuietly(out);
}
}
权限检查
动态权限是通过Context的checkSelfPermission方法来检查的,其实现是在ContextImpl中,直接跳转到相应的方法。
frameworks/base/core/java/android/app/ContextImpl.java
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
throw new IllegalArgumentException("permission is null");
}
final IActivityManager am = ActivityManager.getService();
...
try {
return am.checkPermission(permission, pid, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
从ContextImpl.checkPermission方法源码中,我们很快找到了老熟人ActivityManager的身影,还是老规矩直接跳转到提供对应服务的ActivityManagerService.checkPermission方法中。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
}
return checkComponentPermission(permission, pid, uid, -1, true);
}
int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
return ActivityManager.checkComponentPermission(permission, uid,
owningUid, exported);
}
上面源码最后又回到了 ActivityManager中的checkComponentPermission方法。checkComponentPermission方法中先是对一些固定case作了判断,如果都不满足,最后会调用PackageManager的checkUidPermission方法来查询授权状态。
frameworks/base/core/java/android/app/ActivityManager.java
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
// Root, system server get to do everything.
final int appId = UserHandle.getAppId(uid);
if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
return PackageManager.PERMISSION_GRANTED;
}
// Isolated processes don't get any permissions.
if (UserHandle.isIsolated(uid)) {
return PackageManager.PERMISSION_DENIED;
}
// If there is a uid that owns whatever is being accessed, it has
// blanket access to it regardless of the permissions it requires.
if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
return PackageManager.PERMISSION_GRANTED;
}
// If the target is not exported, then nobody else can get to it.
if (!exported) {
/*
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
here);
*/
return PackageManager.PERMISSION_DENIED;
}
if (permission == null) {
return PackageManager.PERMISSION_GRANTED;
}
try {
return AppGlobals.getPackageManager()
.checkUidPermission(permission, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
XXXManager只是一个辅助类,其真正提供服务的都是XXXManagerService,所以直接跳转PackageManagerService中的checkUidPermission方法。在checkUidPermission方法中,
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@Override
public int checkUidPermission(String permName, int uid) {
synchronized (mPackages) {
final String[] packageNames = getPackagesForUid(uid);
final PackageParser.Package pkg = (packageNames != null && packageNames.length > 0)
? mPackages.get(packageNames[0])
: null;
return mPermissionManager.checkUidPermission(permName, pkg, uid, getCallingUid());
}
}
PackageManagerService中的checkUidPermission似乎也没干什么,直接跳转到PermissionManagerService的checkUidPermission方法。最终在这个方法中会根据各种条件判断返回授权状态。
frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
private int checkUidPermission(String permName, PackageParser.Package pkg, int uid,
int callingUid) {
final int callingUserId = UserHandle.getUserId(callingUid);
final boolean isCallerInstantApp =
mPackageManagerInt.getInstantAppPackageName(callingUid) != null;
final boolean isUidInstantApp =
mPackageManagerInt.getInstantAppPackageName(uid) != null;
final int userId = UserHandle.getUserId(uid);
if (!mUserManagerInt.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}
if (pkg != null) {
if (pkg.mSharedUserId != null) {
if (isCallerInstantApp) {
return PackageManager.PERMISSION_DENIED;
}
} else if (mPackageManagerInt.filterAppAccess(pkg, callingUid, callingUserId)) {
return PackageManager.PERMISSION_DENIED;
}
final PermissionsState permissionsState =
((PackageSetting) pkg.mExtras).getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
if (isUidInstantApp) {
if (mSettings.isPermissionInstant(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
return PackageManager.PERMISSION_GRANTED;
}
}
// Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
ArraySet<String> perms = mSystemPermissions.get(uid);
if (perms != null) {
if (perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
.contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
return PackageManager.PERMISSION_GRANTED;
}
}
}
return PackageManager.PERMISSION_DENIED;
}
总结
本文基于Android 9.0源码分析了动态权限申请和检查的流程,主要是注重流程的学习,一些细节没有指出。下面简单的总结一下。
- 权限申请
(1)App调用requestPermissions发起动态权限申请;
(2)requestPermissions方法通过广播的形式启动PackageInstaller的GrantPermissionsActivity界面让用户选择是否授权;
(3)经由PackageManagerService把相关信息传递给PermissionManagerService处理;
(4)PermissionManagerService处理结束后回调PackageManagerService中onPermissionGranted方法把处理结果返回;
(5)PackageManagerService通知观察者权限变化并调用writeRuntimePermissionsForUserLPr方法让PackageManager的settings记录下相关的权限授予状态。
- 权限检查
(1)App调用checkSelfPermission方法检测是否具有权限;
(2)通过实现类ContextImpl的checkPermission方法经由ActivityManager和ActivityManagerService处理;
(3)经过ActivityManager处理后会调用PackageManagerService的checkUidPermission方法把数据传递给PermissionManagerService处理;
(4)最终经过一系列查询返回权限授权的状态。
前面分析过,申请过的动态权限会被记录在xml文件中,这些文件在PackageManagerService构造方法中会被重新读取并缓存到PackageManager的Settings里面。
mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));